package namespaces

import (
	"errors"
	"fmt"
	"math/rand"
	"sync/atomic"
	"time"

	"gddgame.cc/galaxy/asteroid"
	"gddgame.cc/galaxy/asteroid/cluster"
	"gddgame.cc/galaxy/binocle"
	"gddgame.cc/galaxy/binocle/module/report"
	"gddgame.cc/galaxy/common/service/lobby"
	"gddgame.cc/galaxy/core/sdk"
	"gddgame.cc/galaxy/satellite/formula/nebula"
	"gddgame.cc/galaxy/server/composer"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/def"
)

// Team Namespace
const (
	TeamChange nebula.NSE = iota
	TeamInvite
	TeamJoin
	TeamLeave
	TeamKick
	TeamJoinLobby
	TeamLeaveLobby
)

type TeamManager interface {
	Create(container cluster.Container, people int) string
	Depends() error
	Exist(container cluster.Container, token string) bool
}

type TeamDelegate interface {
	JoinTeam(token string, openId string)
	LeaveTeam(token string, openId string)
	AllowTeam(token string, openId string) error
}

type teamInstance struct {
	TeamDelegate

	master  string
	people  int
	members map[string]*teamMember

	node cluster.Node
}

func (t *teamInstance) UpdateStatus() {
	// 如果房间人数为空，并且发送过finish消息
	i := 0
	changeMaster := true
	for k, _ := range t.members {
		if !t.members[k].Active {
			continue
		}
		if t.master == k {
			changeMaster = false
		}
		i++
	}
	fmt.Println("team num:", i, "change master", changeMaster)
	// 如果有人退出，并且其他所有人都不在激活状态
	if i == 0 {
		// 队伍解散
		for k, _ := range t.members {
			t.TeamDelegate.LeaveTeam(t.node.Token(), k)
		}
		_ = t.node.Close()
	} else if changeMaster {
		var master string
		max := 0
		r := rand.New(rand.NewSource(time.Now().UnixNano()))
		for key, _ := range t.members {
			rr := r.Int()
			if rr > max {
				master = key
			}
		}

		t.master = master
		m := t.members[t.master]
		m.Status = false
		t.node.Broadcast().List(t.master, m.Status, m.Active, m.Location, true).Send(TeamChange)
	}
}

func (t *teamInstance) Token() string {
	return t.node.Token()
}

type teamMember struct {
	OpenId   string
	Status   bool
	Active   bool
	Location string
	node     string
}
type teamService struct {
	builder composer.Builder
	binocle.App
	Delegate TeamDelegate
	// 当前队伍数
	Number int32

	Room RoomManager
}

func (service *teamService) ID() string {
	return "team" + "/" + service.Name()
}
func (service *teamService) Master(node cluster.Node) {

}

func (service *teamService) Init(node cluster.Node) {
	atomic.AddInt32(&service.Number, 1)
	instance := &teamInstance{
		TeamDelegate: service.Delegate,

		node:    node,
		master:  "",
		people:  0,
		members: make(map[string]*teamMember),
	}
	node.SetInstance(instance)
	node.On("members", func(message asteroid.Message) {
		//id := message.GetOrigin()
		openId := message.Data().(string)
		members := instance.members
		for u, _ := range members {
			if u == openId {
				continue
			}
			mm := members[u]
			node.Broadcast().List(u, mm.Status, mm.Active, mm.Location, mm.OpenId == instance.master).Send(TeamJoin)
		}
	})
	node.On("getmembers", func(message asteroid.Message) {
		m := make(map[string]string)
		for u, _ := range instance.members {
			m[u] = instance.members[u].Location
		}
		message.Response(m)
	})
	node.On("people", func(message asteroid.Message) {
		people := message.Data()
		instance.people = utils.ToInt(people)
		fmt.Println("Team people", people)
	})
	node.On(cluster.JoinEvent, func(message asteroid.Message) {
		//id := message.GetOrigin()
		other := message.GetFrom()
		openId := message.Data().(string)

		// 已经加入
		mm, ok := instance.members[openId]
		if ok {
			// 已经加入，重新加入
			mm.Active = true
			mm.Status = false
			mm.node = other

			node.Broadcast().List(openId, mm.Status, mm.Active, mm.Location, mm.OpenId == instance.master).Send(TeamChange)
			service.Delegate.JoinTeam(node.Token(), openId)
			message.Response(true)
		}

		// 满员
		if len(instance.members) == instance.people {
			message.Response(false)
			return
		}
		if len(instance.members) == 0 {
			instance.master = openId
		}

		m := &teamMember{
			OpenId:   openId,
			Status:   false,
			Active:   true,
			Location: "",
			node:     other,
		}
		instance.members[openId] = m
		// 发送给其他用户
		node.Broadcast().List(openId, m.Status, m.Active, m.Location, m.OpenId == instance.master).Send(TeamJoin)
		service.Delegate.JoinTeam(node.Token(), openId)
		message.Response(true)
	})
	node.On(cluster.LeaveEvent, func(message asteroid.Message) {
		//id := message.GetOrigin()
		openId := message.Data().(string)
		mm, ok := instance.members[openId]
		if !ok {
			message.Response(false)
			return
		}
		mm.Active = false
		mm.Status = false
		// 发送给其他用户，当前用户断开: 直接移除master状态
		node.Broadcast().List(openId).List(openId, mm.Status, mm.Active, mm.Location, false).Send(TeamChange)
		instance.UpdateStatus()

		message.Response(true)
	})
	node.On(TeamChange, func(message asteroid.Message) {
		//id := message.GetOrigin()
		openId := message.Data().(string)
		status := utils.ToBool(message.Read(1))
		location := message.Read(2).(string)
		m, ok := instance.members[openId]
		if !ok {
			message.Response(false)
			return
		}
		m.Active = true
		m.Status = status
		m.Location = location

		// 发送给其他用户
		node.Broadcast().List(openId, m.Status, m.Active, m.Location, m.OpenId == instance.master).Send(TeamChange)
		message.Response(true)
	})
	node.On(TeamKick, func(message asteroid.Message) {
		//id := message.GetOrigin()
		openId := message.Data().(string)
		targetId := message.Read(1).(string)
		if instance.master != openId {
			message.Response(false)
			return
		}
		_, ok := instance.members[targetId]
		if !ok {
			message.Response(false)
			return
		}
		delete(instance.members, targetId)
		service.Delegate.LeaveTeam(node.Token(), targetId)
		node.Broadcast().List(targetId).Send(TeamKick)
		instance.UpdateStatus()
	})
	node.On(TeamLeave, func(message asteroid.Message) {
		//id := message.GetOrigin()
		openId := message.Data().(string)
		mm, ok := instance.members[openId]
		if !ok {
			message.Response(false)
			return
		}
		delete(instance.members, openId)
		service.Delegate.LeaveTeam(node.Token(), openId)
		// 发送给其他用户，当前用户已离开
		node.Broadcast().List(mm.OpenId).Send(TeamLeave)
		instance.UpdateStatus()
		message.Response(true)
	})
}
func (service *teamService) InitClient(client *cluster.Client) {
	client.Filter(TeamJoinLobby, func(message asteroid.Message, member def.Member) {
		socket := member.(*nebula.Socket)
		channel := message.Read(1).(string)
		_ = socket.Set("scene/lobby", channel)
		_ = socket.Emit(TeamJoinLobby, message.Read(2))
	})
	// join, leave, change
	client.Filter(asteroid.DefaultFilter, func(message asteroid.Message, member def.Member) {
		socket := member.(*nebula.Socket)
		var nse nebula.NSE
		switch n := message.GetEvent().(type) {
		case nebula.NSE:
			nse = n
		default:
			nse = nebula.NSE(utils.ToByte(n))
		}
		_ = socket.Emit(nse, message.List()...)
	})
}
func (service *teamService) CloseInstance(node cluster.Node) {
	atomic.AddInt32(&service.Number, -1)
	// todo pool
}
func (service *teamService) CloseClient(client *cluster.Client) {

}
func (service *teamService) Create(container cluster.Container, people int) string {
	token := container.Balance().CreateService(service.ID())
	client := container.Balance().GetClient(service.ID(), token)
	client.Emit().Data(people).Send("people")
	return token
}
func (service *teamService) GetMembers(client *cluster.Client, socket *nebula.Socket, openId string) map[string]string {
	rm := make(chan map[string]string)
	client.Emit().CallbackTo(func(success bool, data interface{}) {
		if success {
			members := data.(map[string]string)
			rm <- members
		} else {
			rm <- nil
		}
	}).Send("getmembers")
	return <-rm
}
func (service *teamService) Depends() error {
	service.builder.Factory("room", &service.Room)
	if service.Room == nil {
		return errors.New("Team need Room")
	}
	return nil
}
func (service *teamService) Exist(container cluster.Container, token string) bool {
	client := container.BalanceClient(service.ID(), token)
	return client != nil
}
func TeamHandler(builder composer.Builder, namespace nebula.Namespace, client *sdk.AppClient, delegate TeamDelegate) TeamManager {
	var agent binocle.App
	builder.Factory("agent", &agent)

	teamManager := &teamService{builder: builder, App: agent}
	_ = builder.BindInstance("team", teamManager)

	agent.Cluster().RegisterBalance(teamManager)

	lobbyService := lobby.Default()

	teamManager.Delegate = delegate

	agent.AddMetrics().Period(report.Minute1).Type(report.BothMetric).
		Aggregate(report.Sum, report.Max).
		Collect(func() map[string]float32 {
			return map[string]float32{
				"team.number": float32(teamManager.Number),
			}
		})

	_ = namespace.Connect(func(socket *nebula.Socket, next nebula.Next) {
		// 连接状态验证
		token, ok := socket.Query("token")
		if !ok {
			_ = next(errors.New("Team query error"))
			return
		}
		var openId string
		err := socket.Get("open_id", &openId)
		if err != nil {
			_ = next(errors.New("Auth error"))
			return
		}
		client := agent.BalanceClient(teamManager.ID(), token)
		if client == nil {
			_ = next(errors.New("Team not exist"))
			return
		}
		if err := next(nil); err != nil {
			socket.Disconnect()
			return
		}

		_ = socket.On(TeamChange, func(status bool, location string) {
			client.Emit().Origin(socket).List(openId, status, location).Send(TeamChange)
		})
		_ = socket.On(TeamInvite, func(invited string) bool {
			if err := delegate.AllowTeam(token, invited); err != nil {
				socket.SendError(err)
				return false
			} else {
				agent.Notice(invited, "team/invite", openId, token)
				return true
			}
		})
		_ = socket.On(TeamKick, func(targetId string) {
			client.Emit().Origin(socket).List(openId, targetId).Send(TeamKick)
		})
		_ = socket.On(TeamLeave, func() {
			client.Emit().Origin(socket).List(openId).Send(TeamLeave)
			socket.Disconnect()
		})
		// 进入等待队列: 组队
		_ = socket.On(TeamJoinLobby, func(lobbyName string, options map[string]interface{}) bool {
			m := lobbyService.Get(lobbyName)
			if m == nil {
				return false
			}
			members := teamManager.GetMembers(client, socket, openId)
			if members == nil {
				socket.SendError(errors.New("team get members fail"))
				//builder.Error(err)
				return false
			}
			if m.Need(members, options) {
				channel, payload, err := m.Channel(members, options)
				if err != nil {
					socket.SendError(err)
					return false
				}
				l, err := lobbyService.Join(agent.Hash(), lobbyName, members, channel, payload)

				if err != nil {
					socket.SendError(err)
					//builder.Error(err)
					return false
				}
				client.Broadcast().List(m.ID(), l, len(members)).Send(TeamJoinLobby)
			} else {
				_ = teamManager.Room.Create(agent, members, options)
			}

			return true
		})

		_ = socket.On(TeamLeaveLobby, func() {
			var openId string
			if err := socket.Get("open_id", &openId); err != nil {
				return
			}
			var channel string
			if err := socket.Get("scene/lobby", &channel); err == nil {
				lobbyService.Leave(agent.Hash(), channel, openId, true)
				client.Broadcast().Send(TeamLeaveLobby)
			}
		})
		_ = socket.Disconnected(func(reason string) {
			client.Leave(socket, openId)
		})

		b := client.Join(socket, openId)
		if !b {
			socket.Disconnect()
		} else {
			client.Emit().Origin(socket).List(openId).Send("members")
		}
	})
	return teamManager
}
